"""
Data collection API endpoints

Provides REST API for:
- Collecting data from devices
- Managing device connections
- Cache management
"""

from __future__ import annotations

from datetime import datetime
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_current_user, get_db
from app.connectors.factory import DeviceType
from app.models.user import User
from app.schemas.base import CamelModel
from app.services.data_collection_service import data_collection_service


router = APIRouter()


# Request/Response schemas
class CollectSinglePointRequest(BaseModel):
    """Request to collect single data point"""

    device_id: int = Field(..., description="Device ID")
    device_type: str = Field(..., description="Device type (http, opc_ua, modbus_tcp)")
    config: dict[str, Any] = Field(..., description="Device connection configuration")
    metric_name: str = Field(..., description="Metric name")
    address: str = Field(..., description="Data point address")
    use_cache: bool = Field(default=True, description="Use cache if available")
    cache_ttl: float | None = Field(default=None, description="Cache TTL in seconds")


class CollectMultiplePointsRequest(BaseModel):
    """Request to collect multiple data points"""

    device_id: int = Field(..., description="Device ID")
    device_type: str = Field(..., description="Device type (http, opc_ua, modbus_tcp)")
    config: dict[str, Any] = Field(..., description="Device connection configuration")
    points: list[dict[str, Any]] = Field(
        ..., description="List of points [{'name': 'temp', 'address': '...', 'unit': '°C'}]"
    )


class WriteValueRequest(BaseModel):
    """Request to write value to device"""

    device_id: int = Field(..., description="Device ID")
    device_type: str = Field(..., description="Device type")
    config: dict[str, Any] = Field(..., description="Device connection configuration")
    address: str = Field(..., description="Data point address")
    value: Any = Field(..., description="Value to write")


class DataPointResponse(CamelModel):
    """Data point response"""

    address: str
    value: Any
    quality: str
    timestamp: float
    unit: str | None = None
    metadata: dict[str, Any] | None = None


class CollectionStatusResponse(CamelModel):
    """Collection service status"""

    active_connections: int
    connection_ids: list[int]
    cache_stats: dict[str, Any] | None = None


class WriteValueResponse(CamelModel):
    """Response after writing a value."""

    success: bool
    device_id: int
    address: str
    value: Any


class DisconnectResponse(CamelModel):
    """Response for device disconnect."""

    success: bool
    device_id: int
    message: str


class CacheMessageResponse(CamelModel):
    """Generic cache operation message."""

    message: str


class CacheCleanupResponse(CacheMessageResponse):
    """Cache cleanup response with removed count."""

    removed_count: int


def _normalize_timestamp(value: Any) -> float:
    """Convert datetime or numeric timestamp to float seconds."""
    if isinstance(value, datetime):
        return value.timestamp()
    if isinstance(value, int | float):
        return float(value)
    # Attempt to cast from string representation
    try:
        return float(value)
    except (TypeError, ValueError) as exc:  # pragma: no cover - defensive
        raise ValueError(f"Invalid timestamp value: {value!r}") from exc


@router.post("/collect/single", response_model=DataPointResponse, status_code=status.HTTP_200_OK)
async def collect_single_point(
    request: CollectSinglePointRequest,
    db: AsyncSession = Depends(get_db),
    _current_user: User = Depends(get_current_user),
) -> DataPointResponse:
    """
    Collect single data point from device

    - Connects to device if not already connected
    - Reads data point at specified address
    - Saves to database
    - Returns data point with quality and timestamp
    - Uses cache if enabled (default 60s TTL)
    """
    try:
        # Parse device type
        device_type = DeviceType(request.device_type.lower())
    except ValueError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid device_type: {request.device_type}. "
            f"Must be one of: http, opc_ua, modbus_tcp",
        )

    try:
        data_point = await data_collection_service.collect_single_point(
            db=db,
            device_id=request.device_id,
            device_type=device_type,
            config=request.config,
            metric_name=request.metric_name,
            address=request.address,
            use_cache=request.use_cache,
            cache_ttl=request.cache_ttl,
        )

        return DataPointResponse(
            address=data_point.address,
            value=data_point.value,
            quality=data_point.quality.value,
            timestamp=_normalize_timestamp(data_point.timestamp),
            unit=data_point.unit,
            metadata=data_point.metadata,
        )
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to collect data: {e!s}",
        )


@router.post("/collect/multiple", response_model=list[DataPointResponse], status_code=status.HTTP_200_OK)
async def collect_multiple_points(
    request: CollectMultiplePointsRequest,
    db: AsyncSession = Depends(get_db),
    _current_user: User = Depends(get_current_user),
) -> list[DataPointResponse]:
    """
    Collect multiple data points from device in batch

    - More efficient than multiple single requests
    - All points are collected in one operation
    - Returns list of data points
    """
    try:
        device_type = DeviceType(request.device_type.lower())
    except ValueError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid device_type: {request.device_type}",
        )

    try:
        data_points = await data_collection_service.collect_multiple_points(
            db=db,
            device_id=request.device_id,
            device_type=device_type,
            config=request.config,
            points=request.points,
        )

        return [
            DataPointResponse(
                address=dp.address,
                value=dp.value,
                quality=dp.quality.value,
                timestamp=_normalize_timestamp(dp.timestamp),
                unit=dp.unit,
                metadata=dp.metadata,
            )
            for dp in data_points
        ]
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to collect multiple points: {e!s}",
        )


@router.post("/write", status_code=status.HTTP_200_OK, response_model=WriteValueResponse)
async def write_value(
    request: WriteValueRequest,
    _current_user: User = Depends(get_current_user),
) -> WriteValueResponse:
    """
    Write value to device

    - Writes single value to device at specified address
    - Returns success status
    """
    try:
        device_type = DeviceType(request.device_type.lower())
    except ValueError:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid device_type: {request.device_type}",
        )

    try:
        success = await data_collection_service.write_value(
            device_id=request.device_id,
            device_type=device_type,
            config=request.config,
            address=request.address,
            value=request.value,
        )

        return {
            "success": success,
            "device_id": request.device_id,
            "address": request.address,
            "value": request.value,
        }
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to write value: {e!s}",
        )


@router.delete("/connections/{device_id}", status_code=status.HTTP_200_OK, response_model=DisconnectResponse)
async def disconnect_device(
    device_id: int,
    _current_user: User = Depends(get_current_user),
) -> WriteValueResponse:
    """
    Disconnect specific device

    - Closes connection to device
    - Removes from connection cache
    """
    success = await data_collection_service.disconnect_device(device_id)

    if not success:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Device {device_id} not found in active connections",
        )

    return DisconnectResponse(success=True, device_id=device_id, message="Device disconnected")


@router.get("/status", response_model=CollectionStatusResponse)
async def get_collection_status(
    _current_user: User = Depends(get_current_user),
) -> CollectionStatusResponse:
    """
    Get data collection service status

    - Returns active connection count
    - Lists connected device IDs
    - Shows cache statistics if enabled
    """
    return CollectionStatusResponse(
        active_connections=data_collection_service.get_connection_count(),
        connection_ids=data_collection_service.get_active_connections(),
        cache_stats=data_collection_service.get_cache_stats(),
    )


@router.post("/cache/clear", status_code=status.HTTP_200_OK, response_model=CacheMessageResponse)
async def clear_cache(
    _current_user: User = Depends(get_current_user),
) -> CacheMessageResponse:
    """
    Clear data collection cache

    - Removes all cached data points
    - Forces fresh reads from devices
    """
    data_collection_service.clear_cache()
    return {"message": "Cache cleared successfully"}


@router.post("/cache/cleanup", status_code=status.HTTP_200_OK, response_model=CacheCleanupResponse)
async def cleanup_expired_cache(
    _current_user: User = Depends(get_current_user),
) -> CacheCleanupResponse:
    """
    Clean up expired cache entries

    - Removes only expired entries
    - Returns count of removed entries
    """
    result = data_collection_service.cleanup_expired_cache()
    # Support both int and dict returns for easier testing/mocking
    if isinstance(result, int):
        return {"message": "Expired cache cleaned", "removed_count": int(result)}
    removed = result.get("removed_count")
    if removed is None:
        removed = result.get("cleaned", 0)
    return {"message": result.get("message", "Expired cache cleaned"), "removed_count": int(removed)}
